#! /usr/bin/env python3
# -*- encoding: utf-8 -*-
import asyncio
import logging
import inspect
from aiohttp import web
from urllib import parse
from helper.apiexception import APIException
import os
logging.basicConfig(level=logging.INFO)


# 一些读取request中参数的方法
# VAR_POSITIONAL	一个未绑定到任何其他参数的位置参数元组。这对应*args于Python函数定义中的参数。
# KEYWORD_ONLY	值必须作为关键字参数提供。仅关键字参数是出现在Python函数定义中的条目*或*args条目之后的参数。
# VAR_KEYWORD	不绑定到任何其他参数的关键字参数的字典。这对应**kw于Python函数定义中的参数。
def get_required_kw_args(fn):
    args = []
    params = inspect.signature(fn).parameters
    for name, param in params.items():
        if param.kind == inspect.Parameter.KEYWORD_ONLY and (
                param.default != inspect.Parameter.empty):
            args.append(name)
    return tuple(args)


def get_named_kw_args(fn):
    args = []
    params = inspect.signature(fn).parameters
    for name, param in params.items():
        if param.kind == inspect.Parameter.KEYWORD_ONLY:
            args.append(name)
    return tuple(args)


def has_named_kw_args(fn):
    params = inspect.signature(fn).parameters
    for name, param in params.items():
        if param.kind == inspect.Parameter.KEYWORD_ONLY:
            return True


def has_var_kw_arg(fn):
    params = inspect.signature(fn).parameters
    for name, param in params.items():
        if param.kind == inspect.Parameter.VAR_KEYWORD:
            return True


def has_request_arg(fn):
    sig = inspect.signature(fn)
    params = sig.parameters
    found = False
    for name, param in params.items():
        if name == 'request':
            found = True
            continue
        if found and (param.kind != inspect.Parameter.VAR_POSITIONAL
                      and param.kind != inspect.Parameter.KEYWORD_ONLY
                      and param.kind != inspect.Parameter.VAR_KEYWORD):
            raise ValueError(
                'request parameter must be the last named parameter \
                in function: %s%s' % (fn.__name__, str(sig)))
    return found


class RequestHandle(object):
    def __init__(self, app, fn):
        self._app = app
        self._func = fn
        self._has_request_arg = has_request_arg(fn)
        self._has_var_kw_arg = has_var_kw_arg(fn)
        self._has_named_kw_args = has_named_kw_args(fn)
        self._named_kw_args = get_named_kw_args(fn)
        self._required_kw_args = get_required_kw_args(fn)

    # 处理参数
    async def __call__(self, request):
        kw = None
        if self._has_var_kw_arg or self._has_named_kw_args or (
                self._has_request_arg):
            if request.method == 'POST':
                if not request.content_type:
                    return web.HTTPBadRequest("缺少ContentType")
                ct = request.content_type.lower()
                if ct.startswith('application/json'):
                    params = await request.json()
                    if not isinstance(params, dict):
                        return web.HTTPBadRequest("json必须为实体")
                    kw = dict(**params)
                elif ct.startswith('application/x-www-form-urlencoded'
                                   ) or ct.startswith('multipart/form-data'):
                    params = await request.post()
                    kw = dict(**params)
                else:
                    raise web.HTTPBadRequest("不支持的ContentType: %s" % ct)
            elif request.method == 'GET':
                qs = request.query_string
                if qs:
                    kw = dict()
                    for k, v in parse.parse_qs(qs).items():
                        kw[k] = v[0]
        if kw is None:
            kw = dict(**request.match_info)
        else:
            if not self._has_var_kw_arg and self._named_kw_args:
                # 移除所有unamed参数
                copy = dict()
                for name in self._named_kw_args:
                    if name in kw:
                        copy[name] = kw[name]
                kw = copy
            for k, v in request.match_info.items():
                if k in kw:
                    logging.WARNING("named参数与args参数重复: %s" % k)
                kw[k] = v
        # 检测request参数
        if self._has_request_arg:
            kw['request'] = request
        # 检测必需参数
        if self._required_kw_args:
            for name in self._required_kw_args:
                if name not in kw:
                    return web.HTTPBadRequest("缺少参数:%s" + name)
        logging.info('call with args: %s' % str(kw))
        try:
            # 使用await会报错, 暂时弄不明白
            r = await self._func(**kw)
            return r
        except APIException as e:
            return dict(
                error=e.error,
                errorCode=e.errorCode,
                data=e.data,
                message=e.message)


def add_route(app, fn):
    method = getattr(fn, '__method__', None)
    path = getattr(fn, '__route__', None)
    if path is None or method is None:
        raise ValueError("%s未定义get或者post" % str(fn))
    if not asyncio.iscoroutinefunction and not inspect.isgeneratorfunction:
        fn = asyncio.coroutine(fn)
    logging.info('add route %s %s => %s(%s)' %
                 (method, path, fn.__name__,
                  ', '.join(inspect.signature(fn).parameters.keys())))
    app.router.add_route(method, path, RequestHandle(app, fn))


def add_routes(app, module_name):
    n = module_name.rfind('.')
    if n == (-1):
        mod = __import__(module_name, globals(), locals())
    else:
        name = module_name[n + 1:]
        mod = getattr(
            __import__(module_name[:n], globals(), locals(), [name]), name)
    for attr in dir(mod):
        if attr.startswith("_"):
            continue
        fn = getattr(mod, attr)
        if callable(fn):
            method = getattr(fn, '__method__', None)
            path = getattr(fn, '__route__', None)
            if method and path:
                add_route(app, fn)


def add_static(app):
    path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static')
    app.router.add_static('/static/', path)
    logging.info('add static %s => %s' % ('/static/', path))
